#include <direct.h>

#include "../../rdx/src/lua/src/lauxlib.h"
#include "../tdp2/tdp2_string.hpp"
#include "../tdp2/tdp2_vector.hpp"
#include "../tdp2/tdp2_hash.hpp"
#include "../tdp2/tdp2_threading.hpp"
#include "toolbox.hpp"

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

using namespace TDP::Containers;
using namespace TDP;

static int Toolbox_SHA256(lua_State *L)
{

	char stringout[64];
	size_t l;
	const char *base = lua_tolstring(L, 1, &l);
	TDP::Crypto::SHA256::SHA256State shaState;
	shaState.FeedBytes(base, l);
	shaState.Terminate();
	shaState.Flush(stringout);
	lua_pushlstring(L, stringout, 64);
	return 1;
}

static int Toolbox_MultiExecute(lua_State *L)
{
	Vector<const char *> ptrs;
	Vector<CString> strings;

	int numStrings = lua_objlen(L, 1);

	strings.Resize(NULL, numStrings);
	ptrs.Resize(NULL, numStrings);

	for(int i=0;i<numStrings;i++)
	{
		lua_pushinteger(L, i+1);
		lua_gettable(L, 1);
		const char *str = lua_tostring(L, -1);

		if(!str)
			return 0;

		strings[i].Set(NULL, str);
		ptrs[i] = strings[i].Chars();

		lua_pop(L, 1);
	}

	lua_pushinteger(L, MultiExecute(ptrs, ptrs.Count()));

	return 1;
}

static int Toolbox_RemoveFile(lua_State *L)
{
	remove(lua_tostring(L, 1));
	return 0;
}

static int Toolbox_FileTimestamp(lua_State *L)
{
	HANDLE f = CreateFile(lua_tostring(L, 1), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if(f == INVALID_HANDLE_VALUE)
		return 0;

	FILETIME modTime;
	GetFileTime(f, NULL, NULL, &modTime);
	CloseHandle(f);

	char timestamp[24];
	sprintf(timestamp, "%i|%i", modTime.dwHighDateTime, modTime.dwLowDateTime);
	lua_pushstring(L, timestamp);

	return 1;
}

static int Toolbox_CreateFileDirectories(lua_State *L)
{
	char path[8000];
	strcpy(path, lua_tostring(L, 1));
	size_t len = strlen(path);

	for(size_t i=0;i<len;i++)
	{
		if(path[i] == '/')
		{
			path[i] = '\0';
			_mkdir(path);
			path[i] = '/';
		}
	}

	return 0;
}

static int Toolbox_UnpackBinaryValues(lua_State *L)
{
	size_t nBytes = lua_objlen(L, 1);
	bool bigEndian = (lua_toboolean(L, 2) == 0);
	bool shouldSwap;

	union
	{
		UInt32 v;
		UInt8 b[4];
	} orderCheck;
	orderCheck.v = 1;
	shouldSwap = (bigEndian == (orderCheck.b[0] != 1));

	const char *srcBytes = lua_tostring(L, 1);
	if(!srcBytes)
		return 0;

	size_t nDestBytes = 0;
	for(size_t i=0;i<nBytes;i++)
	{
		size_t stride = static_cast<size_t>(srcBytes[i]);
		nDestBytes += stride;
		i += stride;

		if(i > nBytes || stride > 4)
		{
			fprintf(stderr, "BINARY DATA WAS CORRUPT\n");
			return 0;
		}
	}

	char *dstBytes = new char[nDestBytes];

	nDestBytes = 0;
	for(size_t i=0;i<nBytes;i++)
	{
		size_t stride = static_cast<size_t>(srcBytes[i]);

		if(shouldSwap)
		{
			for(size_t j=0;j<stride;j++)
				dstBytes[nDestBytes+j] = srcBytes[i+stride-j];
		}
		else
		{
			for(size_t j=0;j<stride;j++)
				dstBytes[nDestBytes+j] = srcBytes[i+1+j];
		}
		nDestBytes += stride;
		i += stride;
	}

	lua_pushlstring(L, dstBytes, nDestBytes);

	delete[] dstBytes;
	return 1;
}

static int Toolbox_ScanDirectory(lua_State *L)
{
	const char *dir = lua_tostring(L, 1);
	WIN32_FIND_DATA findData;

	HANDLE h = FindFirstFileA(dir, &findData);
	if(h == INVALID_HANDLE_VALUE)
		return 0;

	lua_createtable(L, 0, 0);
	lua_createtable(L, 0, 0);

	int nDirs = 0;
	int nFiles = 0;

	do
	{
		if( !(findData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) )
		{
			if(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			{
				nDirs++;
				lua_pushinteger(L, nDirs);
				lua_pushstring(L, findData.cFileName);
				lua_rawset(L, -3);
			}
			else
			{
				nFiles++;
				lua_pushinteger(L, nFiles);
				lua_pushstring(L, findData.cFileName);
				lua_rawset(L, -4);
			}
		}
	} while(FindNextFileA(h, &findData));

	FindClose(h);

	return 2;
}

extern "C" __declspec(dllexport) int RegisterToolbox(lua_State *L)
{
	lua_pushcfunction(L, Toolbox_SHA256);
	lua_setglobal(L, "SHA256");
	lua_pushcfunction(L, Toolbox_MultiExecute);
	lua_setglobal(L, "MultiExecute");
	lua_pushcfunction(L, Toolbox_FileTimestamp);
	lua_setglobal(L, "FileTimestamp");
	lua_pushcfunction(L, Toolbox_RemoveFile);
	lua_setglobal(L, "RemoveFile");
	lua_pushcfunction(L, Toolbox_CreateFileDirectories);
	lua_setglobal(L, "CreateFileDirectories");
	lua_pushcfunction(L, Toolbox_UnpackBinaryValues);
	lua_setglobal(L, "UnpackBinaryValues");
	lua_pushcfunction(L, Toolbox_ScanDirectory);
	lua_setglobal(L, "ScanDirectory");
	
	RegisterImageTools(L);

	return 1;
}
